
#pragma message ("take out when refractoring exception handler")
#include <stdlib.h>
#include "..\global\globaldef.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include "Comm.h"
#include "CommStructs.h"
#include "CommServer.h"
#include "PacketConven.h"
#include "..\Global\globaldef.h"

#define TMP_SIZE sizeof(Connection::Packet::Header)

inline unsigned int GetVertexFormatSize(uint format) {

	uint size = 0;
	size += (format & VERTEX_POSITION) ? sizeof(tVector3) : 0;
	size += (format & VERTEX_NORMAL) ? sizeof(tVector3) : 0;
	size += (format & VERTEX_TANGENT) ? sizeof(tVector3) : 0;
	
	size += (format & VERTEX_UV0) ? sizeof(tVector2) : 0;
	size += (format & VERTEX_UV1) ? sizeof(tVector2) : 0;
	size += (format & VERTEX_UV2) ? sizeof(tVector2) : 0;

	size += (format & VERTEX_COLOR) ? sizeof(tVector4) : 0;
	size += (format & VERTEX_DEFORMATION) ? sizeof(tVector2) : 0;

	return size;
}

Connection::Packet::Packet()
{
	pData = NULL;
	header.packetID = 0;
	header.dataSize = 0;
}

void Connection::Packet::ReleaseHeapData()
{
	if(pData) {
		delete [] pData;
		pData = NULL;

		header.dataSize = 0;
	}
}

Connection::Packet::~Packet()
{	}

#define RETRY_LOOP(maxAttempts) { \
	static unsigned int rVar = 0; \
	rVar++; \
	if(rVar > max(0, maxAttempts)) { \
	rVar = 0; \
	break; \
	} else { \
	continue; \
	} \
}

void PrintDebug(const char* format, ...)
{ 
	va_list args;
	char buf[512] = {'\n'};
	char fin[512] = {'\n'};
	va_start(args, format);
	vsprintf(buf, format, args);
	va_end(args);
	sprintf(fin, "%s\n", buf);
	OutputDebugString(fin);

}

DWORD WINAPI Connection::SendThread(void* lpParam)
{ 
	Connection* pClient = (Connection*)lpParam;
	SOCKET Socket = pClient->m_socket;
	uint timeOutCounter = 0;
	while(pClient->m_socketLive) {
	
		/*
		The idea here is to check for activity from the application thread interms of transmitting data
		If there is activity then halt this thread so that the application can aquire the thread lock and
		seamlessly access the que. When no activity is detected this thread will then aquire the lock and
		send  the data
		-------------------------------------------------------------------------------------------------
		*/
		u_int queSize = pClient->g_sendQue.size();
		if(queSize == 0) {
		
			Sleep(5);
			timeOutCounter++;
			if(timeOutCounter > SERVER_TERMINATE_TIMEOUT){
			
				byte b;
				pClient->SendDataPacket(&b, sizeof(byte), NETWORK_PROBE);
			}

			continue;
		}

		timeOutCounter = 0;
		/*for(u_int i = 0; i < 3; i++) {

			if(pClient->g_sendQue.size() != queSize || queSize == 0) {
				
				if(!pClient->m_pLocalServer) {

					pClient->m_timeoutTimer++;
					if(pClient->m_timeoutTimer > (TIMEOUT / 350)) {
						byte b;
						pClient->SendDataPacket(&b, sizeof(byte), NETWORK_PROBE);
					}
				}

				Sleep(100);
				continue;
			}
			else {

				pClient->m_timeoutTimer = 0;
				Sleep(33);
			}
		}*/

		EnterCriticalSection(&pClient->criticalSections[Connection::SEND_THREAD]);
		FOREACH(Connection::Packet, i, pClient->g_sendQue) {
 
			Connection::Packet* pPacket = &(*i);
			byte* pStartPtr = (byte*)&pPacket->header;
			byte* pWritePtr = pStartPtr;
			while(pWritePtr - pStartPtr < sizeof(Connection::Packet::Header)) {
				
				const int sendStatus = send(pClient->m_socket, (const char*)pWritePtr, sizeof(Connection::Packet::Header) - (pWritePtr - pStartPtr), 0);
				if(sendStatus == SOCKET_ERROR) {

					if(NETWORK_PROBE == i->header.packetID)
						pClient->m_socketLive = false;

					RETRY_LOOP(10);
				}

				pWritePtr += sendStatus;
			}

			if(pWritePtr - pStartPtr < sizeof(Connection::Packet::Header)) {
				i->ReleaseHeapData();
				continue;
			}

			pStartPtr = pPacket->pData;
			pWritePtr = pStartPtr;
			while(pWritePtr - pStartPtr < i->header.dataSize) {

				const int sendStatus = send(pClient->m_socket,(const char*)pWritePtr, i->header.dataSize - (pWritePtr - pStartPtr),0);
				if(sendStatus == SOCKET_ERROR)
					RETRY_LOOP(10);

				pWritePtr += sendStatus;
			}

			if(pWritePtr - pStartPtr < i->header.dataSize) {
				i->ReleaseHeapData();
				continue;
			}

			i->ReleaseHeapData();
		}
		pClient->g_sendQue.clear();
		LeaveCriticalSection(&pClient->criticalSections[Connection::SEND_THREAD]);
	}

	return 0;
}

bool Connection::SendDataPacket(void* pData, unsigned int sizeInBytes, int packetID)
{
	if(!pData || sizeInBytes == 0 || m_socketLive == false)
		return false;

	Connection::Packet packet;
	packet.header.packetID = packetID;
	packet.header.dataSize = sizeInBytes;
	packet.pData = new byte [sizeInBytes];
	memcpy(packet.pData, pData, sizeInBytes);
	
	EnterCriticalSection(&criticalSections[Connection::SEND_THREAD]);
	g_sendQue.push_back(packet);
	LeaveCriticalSection(&criticalSections[Connection::SEND_THREAD]);
	return true;
}

bool Connection::HasRecievedPackets() 
{
	return (g_recieveQue.size() > 0 ? true : false) && m_socketLive;
}

bool Connection::GetDataPackets(std::vector<Connection::Packet> *pDataPacketList)
{
	if(!pDataPacketList || m_socketLive == false)
		return false;
	
	/*
	The idea here is similar to that in the SendThread() function except it has been modified accordingly
	Because this function is constantly called we just return if the recieve que is activly changing. The
	assumption is that this function will be called shortly again anyway so only allow it to fully complete
	when the packet que is not actvily changing
	-------------------------------------------------------------------------------------------------
	*/

	/*const u_int queSize = g_recieveQue.size();
	for(u_int i = 0; i < 3; i++) {

		if(g_recieveQue.size() != queSize || queSize == 0) {
		//	Beep(1000,15);
			return true;
		}
		else
			Sleep(33);
	}*/
	
	EnterCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);
	FOREACH(Connection::Packet, i, g_recieveQue) {

		pDataPacketList->push_back(*i);
	}

	g_recieveQue.clear();
	LeaveCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);
	return true;
}

bool Connection::IsConnected()
{
	return m_socketLive;
}
	
DWORD WINAPI Connection::RecieveThread(void* lpParam)
{ 
	Connection* pClient = (Connection*)lpParam;
	SOCKET Socket = pClient->m_socket;

	while(pClient->m_socketLive) {

RESTART:
		Connection::Packet::Header header;
		byte* pStartPtr = (byte*)&header;
		byte* pWritePtr = pStartPtr;
		
		/*
		Read in the actual header of the packet. When data is recieved we want to reset the timeout timer.
		This needs to go in a loop because data isn't always sent in complete blocks.
		-------------------------------------------------------------------------------------------------
		*/
		while(pWritePtr - pStartPtr < sizeof(Connection::Packet::Header)) {
			
			const u_int recvStatus = recv(Socket, (char*)pWritePtr, sizeof(Connection::Packet::Header) - (pWritePtr - pStartPtr), 0);
			
			if(recvStatus == SOCKET_ERROR)
				goto RESTART;

			pWritePtr += recvStatus;
			pClient->m_timeoutTimer = 0;
		}

		const u_int dataSize = header.dataSize;
		if(pWritePtr - pStartPtr < sizeof(Connection::Packet::Header) || dataSize == 0)
			goto RESTART;

		byte* pData = new byte[dataSize];
		if(!pData)
			goto RESTART;
		memset(pData, 0, dataSize);
		pStartPtr = pData;
		pWritePtr = pStartPtr;

		/*
		Since the header has been sent we now have the information for allocating and recieving the actual
		data.
		-------------------------------------------------------------------------------------------------
		*/
		while(pWritePtr - pStartPtr < dataSize) {
			
			const u_int recvStatus = recv(Socket, (char*)pWritePtr, dataSize - (pWritePtr - pStartPtr), 0);

			if(recvStatus == SOCKET_ERROR)
				goto RESTART;

			pWritePtr += recvStatus;
			pClient->m_timeoutTimer = 0;
		}

		if(pWritePtr - pStartPtr < dataSize)
			goto RESTART;
				
		/*
		Handle certian type of traffic internally. Some packets can be internal data used to shutdown or
		probe clients so we dont want to expose this to the external application
		-------------------------------------------------------------------------------------------------
		*/
		const int packetID = header.packetID;
		if(packetID & PACKET_NETWORK) {

			switch(packetID) {
			
				case NETWORK_PROBE: 
				{
					/*
					Respond to a server is alive query with a packet to say 'yes I'm still here' and 
					consequently reset it's time out counter for this client
					-------------------------------------------------------------------------------------------------
					*/
					if(!pClient->m_pLocalServer) {
						byte b;
						pClient->SendDataPacket(&b, sizeof(byte), NETWORK_PROBE);
					}

				} break;

				case NETWORK_DISCONNECT:
				{
					if(!pClient->m_pLocalServer)
						pClient->Release();
					pClient->m_socketLive = false;

				} break;
			}
			
			if(pData)
				delete [] pData;

		}
		else {

			/*
			Setup the data packet and put it onto the packet que. Becasue this que is shared with this thread
			and the application we need to use a critical section
			-------------------------------------------------------------------------------------------------
			*/
			Connection::Packet packet;
			packet.header = header;
			packet.pData = pData;

			EnterCriticalSection(&pClient->criticalSections[Connection::RECIEVE_THREAD]);
			pClient->g_recieveQue.push_back(packet);
			LeaveCriticalSection(&pClient->criticalSections[Connection::RECIEVE_THREAD]);
		}
	}
	return 0;
}
 
Connection::Connection(Server* pLocalServer)
{
	m_hThreads[Connection::RECIEVE_THREAD] = NULL;

	m_timeoutTimer = 0;
	m_socket = INVALID_SOCKET;
	m_socketLive = false;
	InitializeCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);
	InitializeCriticalSection(&criticalSections[Connection::SEND_THREAD]);

	m_pLocalServer = pLocalServer;
}

Connection::~Connection()
{
	Release();
	DeleteCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);
}

bool Connection::Initalize(const char* hostname, bool isname)
{
	SOCKET connectionSocket = INVALID_SOCKET;
	struct hostent *host;
	SOCKADDR_IN SockAddr;

	VTRY
	{
		if(hostname == NULL)
			VTHROW("Bad parameters");

		connectionSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
		if(connectionSocket == INVALID_SOCKET)
			VTHROW("Socket creation failed");


		if(isname) {
			if((host = gethostbyname(hostname)) == NULL)
				VTHROW("Unable to resolve host name");
		}
		else {
			unsigned int addrlen = strlen(hostname);
			if((host = gethostbyaddr(hostname,addrlen, AF_INET )) == NULL)
				VTHROW("Unable to resolve host name");
		}


		SockAddr.sin_port = htons(COMM_PORT_I);
		SockAddr.sin_family = AF_INET;
		SockAddr.sin_addr.s_addr = *((unsigned long*)host->h_addr);

		if(connect(connectionSocket,(LPSOCKADDR)(&SockAddr),sizeof(SockAddr)) == SOCKET_ERROR)
			VTHROW("Connect failed");

		if(Initalize(connectionSocket) == false)
			VTHROW("Initalize failed");
 
		return true;

	}VCATCHONLY;

	m_socketLive = false;
	return false;
}

bool Connection::Initalize(SOCKET clientSocket)
{
	HANDLE hThreads[Connection::THREAD_COUNT] = {NULL};

	VTRY
	{
		hThreads[Connection::RECIEVE_THREAD] = CreateThread(NULL, 0, RecieveThread, (void*)this, CREATE_SUSPENDED, NULL);
		hThreads[Connection::SEND_THREAD] = CreateThread(NULL, 0, SendThread, (void*)this, CREATE_SUSPENDED, NULL);

		if(	hThreads[Connection::RECIEVE_THREAD] == NULL || hThreads[Connection::SEND_THREAD] == NULL) 
			VTHROW("Thread creation failed");

		g_sendQue.clear();
		g_recieveQue.clear();

		m_socketLive = true;
		m_socket = clientSocket;
		m_timeoutTimer = 0;

		m_hThreads[Connection::RECIEVE_THREAD] = hThreads[Connection::RECIEVE_THREAD];
		m_hThreads[Connection::SEND_THREAD] = hThreads[Connection::SEND_THREAD];

		if(	ResumeThread(hThreads[Connection::RECIEVE_THREAD]) < 0 || ResumeThread(hThreads[Connection::SEND_THREAD]) < 0)
			VTHROW("Thread execution failed");
		
		return true;

	}VCATCHONLY;

	if(hThreads[Connection::RECIEVE_THREAD]) {
		TerminateThread(hThreads[Connection::RECIEVE_THREAD],-1);
		CloseHandle(hThreads[Connection::RECIEVE_THREAD]);
		m_hThreads[Connection::RECIEVE_THREAD] = NULL;
	}

	if(hThreads[Connection::SEND_THREAD]) {
		TerminateThread(hThreads[Connection::SEND_THREAD],-1);
		CloseHandle(hThreads[Connection::SEND_THREAD]);
		m_hThreads[Connection::SEND_THREAD] = NULL;
	}

	m_socketLive = false;
	closesocket(m_socket);
	m_socket = INVALID_SOCKET;
	
	return false;
}

bool Connection::Release()
{
	byte b;
	SendDataPacket(&b, sizeof(byte), NETWORK_DISCONNECT);

	bool result = true;
	m_socketLive = false;

	if(m_hThreads[Connection::RECIEVE_THREAD]) {
		result = result && (TerminateThread(m_hThreads[Connection::RECIEVE_THREAD],-1) != 0);
		result = result && (CloseHandle(m_hThreads[Connection::RECIEVE_THREAD]) != 0);
		m_hThreads[Connection::RECIEVE_THREAD] = NULL;
	}

	if(m_hThreads[Connection::SEND_THREAD]) {
		if(WAIT_FAILED == WaitForSingleObject(m_hThreads[Connection::SEND_THREAD], 5000)) {
			result = false;
			TerminateThread(m_hThreads[Connection::SEND_THREAD],-1);
		}
		result = result && (CloseHandle(m_hThreads[Connection::SEND_THREAD]) != 0);
		m_hThreads[Connection::SEND_THREAD] = NULL;
	}

	result = result && (shutdown(m_socket,SD_BOTH) == 0);
	result = result && (closesocket(m_socket) == 0);
	m_timeoutTimer = 0;
	
	//TODO:
	//memory leaks if head memory from elements not released
	EnterCriticalSection(&criticalSections[Connection::SEND_THREAD]);
	g_sendQue.clear();
	LeaveCriticalSection(&criticalSections[Connection::SEND_THREAD]);

	EnterCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);
	g_recieveQue.clear();
	LeaveCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);

	m_socket = INVALID_SOCKET;

	return result;
}

//void Connection::SetServerID(int id) 
//{
//	m_serverID = id;
//}

void Connection::GetDebugString(char* buff, unsigned int buffLen)
{
	if(!buff || buffLen == 0)
		return;

	EnterCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);	
	unsigned int recvQue = g_recieveQue.size();
	LeaveCriticalSection(&criticalSections[Connection::RECIEVE_THREAD]);

}